Skip to content

[CFX-5789] [CFX-5790] ToolRegistry & Surface environment-aware failure messaging#577

Merged
taras-pokornyy merged 11 commits into
datarobot-oss:mainfrom
taras-pokornyy:CFX-5789_TOOL_RGISTRY
Jun 18, 2026
Merged

[CFX-5789] [CFX-5790] ToolRegistry & Surface environment-aware failure messaging#577
taras-pokornyy merged 11 commits into
datarobot-oss:mainfrom
taras-pokornyy:CFX-5789_TOOL_RGISTRY

Conversation

@taras-pokornyy

@taras-pokornyy taras-pokornyy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

RATIONALE

When dr deps install runs a tool's install command and it fails, the previous error was a bare exit-code line with no guidance on what to try next. This change wires up the existing ToolRegistry data to produce actionable, environment-aware tips when an install command fails — "You have pyenv — try: pip install uv" — and falls back to the canonical install command (e.g. curl | sh) when no manager is detected. Permission-denied failures (exit code 126 or matching stderr text) get an OS-specific "re-run with sudo / as Administrator" message instead.

CHANGES

PR Automation

Comment-Commands: Trigger CI by commenting on the PR:

  • /trigger-smoke-test or /trigger-test-smoke - Run smoke tests
  • /trigger-install-test or /trigger-test-install - Run installation tests

Labels: Apply labels to trigger workflows:

  • run-smoke-tests or go - Run smoke tests on demand (only works for non-forked PRs)

Important

For Forked PRs: The run-smoke-tests label won't work. A required Smoke Tests check will block merge until a maintainer acts:

  • A maintainer uses /approve-smoke-tests to run smoke tests (results will set the check)
  • A maintainer uses /skip-smoke-tests to bypass the check without running tests

Please comment requesting a maintainer review if you need smoke tests to run.


Note

Low Risk
UX and messaging only around failed installs; success path unchanged. Suggestions are advisory shell commands, not auto-executed.

Overview
When dr deps install fails, users now get structured output on the writer (not only a minimal error): what was tried, optional tips, retry command, and docs URL. InstallPrerequisites tees install output into a buffer so failures can be classified and messaged.

A new ToolRegistry maps tools (python, uv, node, pulumi, task, git) to ordered install strategies (pyenv/brew/asdf/winget/etc.) plus fallbacks. DetectEnvironment probes PATH and NVM layout; selectInstallStrategy picks the first matching manager, skipping the manager that just failed. buildInstallFailureMsg / buildInstallTip add OS-specific guidance for permission errors (exit 126 / stderr heuristics) vs alternate install commands (e.g. “You have pyenv — try: pip install uv”). Returned errors are shortened; detail lives in stdout.

Broad unit coverage was added for registry, strategy selection, and failure messaging.

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

@taras-pokornyy taras-pokornyy self-assigned this Jun 16, 2026

fmt.Fprint(w, msg)

return installed, fmt.Errorf("install failed for %q (exit code %d)", prerequisite.Name, exitCode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start flow hides install failures

High Severity

Detailed install failure text is written only to the io.Writer, while the returned error is a short exit-code message. The quickstart TUI installs deps into a buffer and on failure displays only m.err, so users lose tips, the tried command, and streamed command output that this change added.

Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules for DataRobot CLI

Reviewed by Cursor Bugbot for commit 29b422a. Configure here.

Comment thread internal/dependencies/installer.go
Comment thread internal/dependencies/installer.go
@taras-pokornyy

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 3 total unresolved issues (including 1 from previous review).

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

Comment thread internal/dependencies/installer.go
// Unix-like systems (Linux & macOS) return 126 when a file lacks execute permissions
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && exitCode == 126 {
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exit 126 ignored on Windows

Medium Severity

Permission-denied handling treats exit code 126 only when runtime.GOOS is linux or darwin. Installs still run via sh -c on Windows (e.g. Git Bash), where 126 can mean “not executable,” but without matching stderr text the failure is not classified as permission denied and users miss the Administrator-oriented tip.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules for DataRobot CLI

Reviewed by Cursor Bugbot for commit 6c10ba7. Configure here.

@taras-pokornyy
taras-pokornyy requested a review from a team June 16, 2026 17:32
Comment thread internal/dependencies/registry.go
func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit here:

Do we have reusable values in this repo. so we're not using magic strings for things like "windows" and likely "darwin" and "linux" ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"windows", "darwin", and "linux" are all potential values of runtime.GOOS, which is a build-time constant.

https://pkg.go.dev/internal/goos#GOOS
https://cs.opensource.google/go/go/+/refs/tags/go1.26.4:src/internal/goos/zgoos_linux.go;l=7

I am not recommending this because I'm not convinced it's worth it here but one way to approach this would be to conditionally compile the fallbackstrategy code using go build. (You can extend packages based on runtime.GOOS and runtime.GOARCH.) A working example of this can be found in internal/workload where Wojciech has implemented OS-specific diskspace implementations.

What MIGHT be worth doing is something like this, where we enumerate the OS we actually support.

     type OS string

     const (
         OSDarwin  OS = "darwin"
         OSLinux   OS = "linux"
         OSWindows OS = "windows"
     )

     func IsWindows(os OS) bool { return os == OSWindows }
     func IsUnixLike(os OS) bool { return os == OSDarwin || os == OSLinux }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all just informational though, since I know @taras-pokornyy you want a bigger discussion on how we can scale this registry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a good idea to have constants for GOOS instead of magic strings but not in the scope of this PR 🤷‍♂️

Comment thread internal/dependencies/registry.go Outdated
return ""

case 1:
return " Try: " + cmds[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So wasn't sure where to put this.

Curious what you and @ajalon1 and others think.

I like your usage of \t elsewhere and wonder if that would be better to use than the many places you're using double spaces??

Suggested change
return " Try: " + cmds[0]
return "\tTry: " + cmds[0]

To me it's:

  • less likely to accidentally have one or three spaces at some point as a UI bug
  • easier to read in the code as opposed to empty spaces - maybe that's just me though

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be good to stick to a single convention, but that becomes a renderer / UI issue and I think we can do this better with a more structured formatting approach than just \t. text/tabwriter is a helpful package if we want to go that route.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point.
First level indention was two spaces. Next level indention was "\t" (It is much longer).

Created a const TAB which is two spaces and used that for indentions.
First level indention is just TAB
Next level indention is TAB+TAB

Commit : dc1017f

@c-h-russell-walker c-h-russell-walker left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great - made some nit comments. And thanks for the demo today during our meeting.

@ajalon1 ajalon1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM as is. The registry could use some more work, but that is out of scope.

The one thing I would suggest addressing is what happens with strategy.WithVersion(""). You can either do that here or in a followup.

Comment thread internal/dependencies/registry.go Outdated
func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"windows", "darwin", and "linux" are all potential values of runtime.GOOS, which is a build-time constant.

https://pkg.go.dev/internal/goos#GOOS
https://cs.opensource.google/go/go/+/refs/tags/go1.26.4:src/internal/goos/zgoos_linux.go;l=7

I am not recommending this because I'm not convinced it's worth it here but one way to approach this would be to conditionally compile the fallbackstrategy code using go build. (You can extend packages based on runtime.GOOS and runtime.GOARCH.) A working example of this can be found in internal/workload where Wojciech has implemented OS-specific diskspace implementations.

What MIGHT be worth doing is something like this, where we enumerate the OS we actually support.

     type OS string

     const (
         OSDarwin  OS = "darwin"
         OSLinux   OS = "linux"
         OSWindows OS = "windows"
     )

     func IsWindows(os OS) bool { return os == OSWindows }
     func IsUnixLike(os OS) bool { return os == OSDarwin || os == OSLinux }

Comment thread internal/dependencies/installer.go
func (fs FallbackStrategy) getStrategyTip(goos string) string {
cmds := fs.Commands

if goos == "windows" && len(fs.CommandsWindows) > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all just informational though, since I know @taras-pokornyy you want a bigger discussion on how we can scale this registry.

Comment thread internal/dependencies/installer.go
Comment thread internal/dependencies/installer.go
Comment thread internal/dependencies/registry.go Outdated
return ""

case 1:
return " Try: " + cmds[0]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be good to stick to a single convention, but that becomes a renderer / UI issue and I think we can do this better with a more structured formatting approach than just \t. text/tabwriter is a helpful package if we want to go that route.

@taras-pokornyy

Copy link
Copy Markdown
Contributor Author

Hey @c-h-russell-walker , @ajalon1 !
Thank you for reviews and comments. I really appreciate that 😀
I believe I have fixed all the issues except GOOS magic strings.

Going to merge this guy tonight.
Please let me know in case of anything 😉

@ajalon1 ajalon1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still LGTM pending smoke test results

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, good test

@taras-pokornyy

Copy link
Copy Markdown
Contributor Author

/approve-smoke-tests

@github-actions

Copy link
Copy Markdown
Contributor

🔐 Fork PR smoke tests triggered by @taras-pokornyy

⚠️ Security Notice: This will run tests with access to repository secrets.

What happens next:

  1. Security scans will run automatically (Trivy, gosec)
  2. If security scans pass, smoke tests will run
  3. Results will be posted as PR comments

⚠️ Important: Review the PR code carefully before approving!

@github-actions

Copy link
Copy Markdown
Contributor

🔐 Fork smoke tests started by maintainer

⏳ Security scans passed. Running smoke tests...

Commit: 43727f4adb872ae30a323dd599fb5ae28c49c5e5
View run

@github-actions

Copy link
Copy Markdown
Contributor

All smoke tests passed! (Fork PR)

✅ Security Scan: success
✅ Linux: success
✅ Windows: success

View run details

@taras-pokornyy
taras-pokornyy merged commit 3dedc6a into datarobot-oss:main Jun 18, 2026
31 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants